﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls.Primitives;
using System.Speech.Recognition;
using System.Windows.Input;

namespace Kokomo.Speech
{
	public class SpeechRecognizedEventArgs : RoutedEventArgs
	{
		public SpeechRecognizedEventArgs(RecognitionResult result)
		{
			if (result == null) throw new ArgumentNullException("result");
			this.Result = result;
		}

		public RecognitionResult Result { get; private set; }
	}

	public class SpeechManager
	{

		static SpeechManager()
		{
			ActiveGrammars = new List<Grammar>();
		}

		static void Recognizer_SpeechHypothesized(object sender, SpeechHypothesizedEventArgs e)
		{
		}

		static void Recognizer_SpeechDetected(object sender, SpeechDetectedEventArgs e)
		{
		}

		static void Recognizer_SpeechRecognized(object sender, System.Speech.Recognition.SpeechRecognizedEventArgs e)
		{
			UIElement element = Keyboard.FocusedElement as UIElement;
			if (element != null)
			{
				element.RaiseEvent(new SpeechRecognizedEventArgs(e.Result) { RoutedEvent = SpeechRecognizedRoutedEvent });
			}
		}

		public static void Activate()
		{
			if (Recognizer == null)
			{
				Recognizer = new SpeechRecognizer();
			}

			Recognizer.Enabled = true;
		}

		private static SpeechRecognizer _recognizer;
		private static SpeechRecognizer Recognizer
		{
			get { return _recognizer; }
			set
			{
				if (_recognizer != null)
				{
					_recognizer.SpeechDetected -= new EventHandler<SpeechDetectedEventArgs>(Recognizer_SpeechDetected);
					_recognizer.SpeechHypothesized -= new EventHandler<SpeechHypothesizedEventArgs>(Recognizer_SpeechHypothesized);
					_recognizer.SpeechRecognized -= new EventHandler<System.Speech.Recognition.SpeechRecognizedEventArgs>(Recognizer_SpeechRecognized);
				}
				_recognizer = value;
				if (value != null)
				{
					value.SpeechDetected += new EventHandler<SpeechDetectedEventArgs>(Recognizer_SpeechDetected);
					value.SpeechHypothesized += new EventHandler<SpeechHypothesizedEventArgs>(Recognizer_SpeechHypothesized);
					value.SpeechRecognized += new EventHandler<System.Speech.Recognition.SpeechRecognizedEventArgs>(Recognizer_SpeechRecognized);

					foreach (var grammar in ActiveGrammars)
					{
						_recognizer.LoadGrammar(grammar);
					}
				}
			}
		}

		private static List<Grammar> ActiveGrammars { get; set; }
		private static void LoadGrammar(Grammar grammar)
		{
			if (grammar == null) throw new ArgumentNullException("grammar");

			ActiveGrammars.Add(grammar);
			if (Recognizer != null)
			{
				Recognizer.LoadGrammar(grammar);
			}
		}
		private static void UnloadGrammar(Grammar grammar)
		{
			if (grammar == null) throw new ArgumentNullException("grammar");

			ActiveGrammars.Remove(grammar);
			if (Recognizer != null)
			{
				Recognizer.UnloadGrammar(grammar);
			}
		}

		#region PreviewSpeechRecognizedRoutedEvent
		public static readonly RoutedEvent PreviewSpeechRecognizedRoutedEvent = EventManager.RegisterRoutedEvent("PreviewSpeechRecognized", RoutingStrategy.Tunnel, typeof(EventHandler<SpeechRecognizedEventArgs>), typeof(SpeechManager));
		public static void AddPreviewSpeechRecognizedHandler(UIElement target, EventHandler<SpeechRecognizedEventArgs> handler)
		{
			target.AddHandler(PreviewSpeechRecognizedRoutedEvent, handler);
		}
		public static void RemovePreviewSpeechRecognizedHandler(UIElement target, EventHandler<SpeechRecognizedEventArgs> handler)
		{
			target.RemoveHandler(PreviewSpeechRecognizedRoutedEvent, handler);
		}
		#endregion

		#region SpeechRecognizedRoutedEvent
		public static readonly RoutedEvent SpeechRecognizedRoutedEvent = EventManager.RegisterRoutedEvent("SpeechRecognized", RoutingStrategy.Bubble, typeof(EventHandler<SpeechRecognizedEventArgs>), typeof(SpeechManager));
		public static void AddSpeechRecognizedHandler(UIElement target, EventHandler<SpeechRecognizedEventArgs> handler)
		{
			target.AddHandler(SpeechRecognizedRoutedEvent, handler);
		}
		public static void RemoveSpeechRecognizedHandler(UIElement target, EventHandler<SpeechRecognizedEventArgs> handler)
		{
			target.RemoveHandler(SpeechRecognizedRoutedEvent, handler);
		}
		#endregion

		#region IsSpeechScope
		public static bool GetIsSpeechScope(DependencyObject obj)
		{
			return (bool)obj.GetValue(IsSpeechScopeProperty);
		}

		public static void SetIsSpeechScope(DependencyObject obj, bool value)
		{
			obj.SetValue(IsSpeechScopeProperty, value);
		}

		// Using a DependencyProperty as the backing store for IsSpeechScope.  This enables animation, styling, binding, etc...
		public static readonly DependencyProperty IsSpeechScopeProperty =
			DependencyProperty.RegisterAttached("IsSpeechScope", typeof(bool), typeof(SpeechManager), new UIPropertyMetadata(false, OnIsSpeechScopeChanged));

		static void OnIsSpeechScopeChanged(DependencyObject target, DependencyPropertyChangedEventArgs e)
		{
			bool isScope = (bool)e.NewValue;
			UIElement element = target as UIElement;
			if (element != null)
			{
				if (isScope)
				{
					AddSpeechRecognizedHandler(element, Element_SpeechRecognized);
					element.IsKeyboardFocusWithinChanged += new DependencyPropertyChangedEventHandler(Element_IsKeyboardFocusWithinChanged);
				}
				else
				{
					RemoveSpeechRecognizedHandler(element, Element_SpeechRecognized);
					element.IsKeyboardFocusWithinChanged -= new DependencyPropertyChangedEventHandler(Element_IsKeyboardFocusWithinChanged);
				}
			}
		}

		static void Element_IsKeyboardFocusWithinChanged(object sender, DependencyPropertyChangedEventArgs e)
		{
			DependencyObject target = (DependencyObject)sender;
			SpeechBindingCollection bindings = GetSpeechBindings(target);
			if (bindings != null)
			{
				Grammar grammar = bindings.GetGrammar();
				if (grammar != null)
				{
					bool isFocused = (bool)e.NewValue;
					if (isFocused)
					{
						LoadGrammar(grammar);
					}
					else
					{
						UnloadGrammar(grammar);
					}
				}
			}
		}

		static void Element_SpeechRecognized(object sender, SpeechRecognizedEventArgs e)
		{
			FrameworkElement target = (FrameworkElement)sender;
			SpeechBindingCollection bindings = GetSpeechBindings(target);
			if (bindings != null)
			{
				string phrase = e.Result.Text;
				foreach (var binding in bindings)
				{
					bool matches = string.Equals(phrase, binding.Phrase, StringComparison.InvariantCultureIgnoreCase);
					if (matches)
					{
						if (binding.CommandName != null)
						{
							ISpeechScope scope = target.DataContext as ISpeechScope;
							if (scope != null)
							{
								e.Handled = scope.HandleCommand(binding.CommandName, binding.CommandArgument);
								break;
							}
						}
					}
				}
			}
		}
		#endregion

		#region SpeechBindings
		public static SpeechBindingCollection GetSpeechBindings(DependencyObject obj)
		{
			return (SpeechBindingCollection)obj.GetValue(SpeechBindingsProperty);
		}

		public static void SetSpeechBindings(DependencyObject obj, SpeechBindingCollection value)
		{
			obj.SetValue(SpeechBindingsProperty, value);
		}

		// Using a DependencyProperty as the backing store for SpeechBindings.  This enables animation, styling, binding, etc...
		public static readonly DependencyProperty SpeechBindingsProperty =
			DependencyProperty.RegisterAttached("SpeechBindings", typeof(SpeechBindingCollection), typeof(SpeechManager), new UIPropertyMetadata(null, OnSpeechBindingsChanged));

		static void OnSpeechBindingsChanged(DependencyObject target, DependencyPropertyChangedEventArgs e)
		{
			if (e.NewValue != null)
			{
				SetIsSpeechScope(target, true);
			}
		}
		#endregion
	}
}
